<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.3.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/xlrblog/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/xlrblog/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/xlrblog/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/xlrblog/images/logo.svg" color="#222">

<link rel="stylesheet" href="/xlrblog/css/main.css">


<link rel="stylesheet" href="/xlrblog/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"gitee.com","root":"/xlrblog/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.json"};
  </script>

  <meta name="description" content="微服务保护（初识Sentinel，流量控制，隔离和降级，授权规则） 初识Sentinel雪崩问题及解决方案雪崩问题微服务中，服务间调用关系错综复杂，一个微服务往往依赖于多个其它微服务。 如果服务提供者I发生了故障，当前的应用的部分业务因为依赖于服务I，因此也会被阻塞。此时，其它不依赖于服务I的业务似乎不受影响。 但是，依赖服务I的业务请求被阻塞，用户不会得到响应，则tomcat的这个线程不会释放，">
<meta property="og:type" content="article">
<meta property="og:title" content="微服务保护">
<meta property="og:url" content="https://gitee.com/xlr0306/2023/10/23/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4/index.html">
<meta property="og:site_name" content="君不见的博客">
<meta property="og:description" content="微服务保护（初识Sentinel，流量控制，隔离和降级，授权规则） 初识Sentinel雪崩问题及解决方案雪崩问题微服务中，服务间调用关系错综复杂，一个微服务往往依赖于多个其它微服务。 如果服务提供者I发生了故障，当前的应用的部分业务因为依赖于服务I，因此也会被阻塞。此时，其它不依赖于服务I的业务似乎不受影响。 但是，依赖服务I的业务请求被阻塞，用户不会得到响应，则tomcat的这个线程不会释放，">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://gitee.com/xlrblog/assets/image-20210716113147176.png">
<meta property="og:image" content="https://gitee.com/xlrblog/assets/image-20210716113426524-1697900601549.png">
<meta property="article:published_time" content="2023-10-23T12:13:56.000Z">
<meta property="article:modified_time" content="2023-10-23T12:17:57.145Z">
<meta property="article:author" content="君不见">
<meta property="article:tag" content="SpringCloud">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://gitee.com/xlrblog/assets/image-20210716113147176.png">

<link rel="canonical" href="https://gitee.com/xlr0306/2023/10/23/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>微服务保护 | 君不见的博客</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/xlrblog/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">君不见的博客</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">记录生活中的点点滴滴</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/xlrblog/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/xlrblog/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/xlrblog/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/xlrblog/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://gitee.com/xlr0306/2023/10/23/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/xlrblog/images/avatar.gif">
      <meta itemprop="name" content="君不见">
      <meta itemprop="description" content="君不见，黄河之水天上来，奔流到海不复回。">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="君不见的博客">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          微服务保护
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
              

              <time title="创建时间：2023-10-23 20:13:56 / 修改时间：20:17:57" itemprop="dateCreated datePublished" datetime="2023-10-23T20:13:56+08:00">2023-10-23</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/xlrblog/categories/Java/" itemprop="url" rel="index"><span itemprop="name">Java</span></a>
                </span>
                  ，
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/xlrblog/categories/Java/SpringCloud/" itemprop="url" rel="index"><span itemprop="name">SpringCloud</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p>微服务保护（初识Sentinel，流量控制，隔离和降级，授权规则）</p>
<h2 id="初识Sentinel"><a href="#初识Sentinel" class="headerlink" title="初识Sentinel"></a>初识Sentinel</h2><h3 id="雪崩问题及解决方案"><a href="#雪崩问题及解决方案" class="headerlink" title="雪崩问题及解决方案"></a>雪崩问题及解决方案</h3><h4 id="雪崩问题"><a href="#雪崩问题" class="headerlink" title="雪崩问题"></a>雪崩问题</h4><p>微服务中，服务间调用关系错综复杂，一个微服务往往依赖于多个其它微服务。</p>
<p>如果服务提供者I发生了故障，当前的应用的部分业务因为依赖于服务I，因此也会被阻塞。此时，其它不依赖于服务I的业务似乎不受影响。</p>
<p>但是，依赖服务I的业务请求被阻塞，用户不会得到响应，则tomcat的这个线程不会释放，于是越来越多的用户请求到来，越来越多的线程会阻塞。</p>
<p>服务器支持的线程和并发数有限，请求一直阻塞，会导致服务器资源耗尽，从而导致所有其它服务都不可用，那么当前服务也就不可用了。</p>
<p>那么，依赖于当前服务的其它服务随着时间的推移，最终也都会变的不可用，形成级联失败，雪崩就发生了。</p>
<p>解决雪崩问题的常见方式有四种。</p>
<h4 id="超时处理"><a href="#超时处理" class="headerlink" title="超时处理"></a>超时处理</h4><p>超时处理：设定超时时间，请求超过一定时间没有响应就返回错误信息，不会无休止等待</p>
<h4 id="仓壁模式"><a href="#仓壁模式" class="headerlink" title="仓壁模式"></a>仓壁模式</h4><p>仓壁模式</p>
<p>仓壁模式来源于船舱的设计：</p>
<p>船舱都会被隔板分离为多个独立空间，当船体破损时，只会导致部分空间进入，将故障控制在一定范围内，避免整个船体都被淹没。</p>
<p>于此类似，我们可以限定每个业务能使用的线程数，避免耗尽整个tomcat的资源，因此也叫线程隔离。</p>
<h4 id="断路器"><a href="#断路器" class="headerlink" title="断路器"></a>断路器</h4><p>断路器模式：由<strong>断路器</strong>统计业务执行的异常比例，如果超出阈值则会<strong>熔断</strong>该业务，拦截访问该业务的一切请求。</p>
<p>断路器会统计访问某个服务的请求数量，异常比例。</p>
<p>当发现访问服务D的请求异常比例过高时，认为服务D有导致雪崩的风险，会拦截访问服务D的一切请求，形成熔断。</p>
<h4 id="限流"><a href="#限流" class="headerlink" title="限流"></a>限流</h4><p><strong>流量控制</strong>：限制业务访问的QPS，避免服务因流量的突增而故障。</p>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>什么是雪崩问题？</p>
<ul>
<li>微服务之间相互调用，因为调用链中的一个服务故障，引起整个链路都无法访问的情况。</li>
</ul>
<p>可以认为：</p>
<p><strong>限流</strong>是对服务的保护，避免因瞬间高并发流量而导致服务故障，进而避免雪崩。是一种<strong>预防</strong>措施。</p>
<p><strong>超时处理、线程隔离、降级熔断</strong>是在部分服务故障时，将故障控制在一定范围，避免雪崩。是一种<strong>补救</strong>措施。</p>
<h3 id="服务保护技术对比"><a href="#服务保护技术对比" class="headerlink" title="服务保护技术对比"></a>服务保护技术对比</h3><p>在SpringCloud当中支持多种服务保护技术：</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://github.com/Netflix/Hystrix">Netfix Hystrix</a></li>
<li><a target="_blank" rel="noopener" href="https://github.com/alibaba/Sentinel">Sentinel</a></li>
<li><a target="_blank" rel="noopener" href="https://github.com/resilience4j/resilience4j">Resilience4J</a></li>
</ul>
<p>早期比较流行的是Hystrix框架，但目前国内实用最广泛的还是阿里巴巴的Sentinel框架，对比：</p>
<table>
<thead>
<tr>
<th></th>
<th><strong>Sentinel</strong></th>
<th><strong>Hystrix</strong></th>
</tr>
</thead>
<tbody><tr>
<td>隔离策略</td>
<td>信号量隔离</td>
<td>线程池隔离&#x2F;信号量隔离</td>
</tr>
<tr>
<td>熔断降级策略</td>
<td>基于慢调用比例或异常比例</td>
<td>基于失败比率</td>
</tr>
<tr>
<td>实时指标实现</td>
<td>滑动窗口</td>
<td>滑动窗口（基于 RxJava）</td>
</tr>
<tr>
<td>规则配置</td>
<td>支持多种数据源</td>
<td>支持多种数据源</td>
</tr>
<tr>
<td>扩展性</td>
<td>多个扩展点</td>
<td>插件的形式</td>
</tr>
<tr>
<td>基于注解的支持</td>
<td>支持</td>
<td>支持</td>
</tr>
<tr>
<td>限流</td>
<td>基于 QPS，支持基于调用关系的限流</td>
<td>有限的支持</td>
</tr>
<tr>
<td>流量整形</td>
<td>支持慢启动、匀速排队模式</td>
<td>不支持</td>
</tr>
<tr>
<td>系统自适应保护</td>
<td>支持</td>
<td>不支持</td>
</tr>
<tr>
<td>控制台</td>
<td>开箱即用，可配置规则、查看秒级监控、机器发现等</td>
<td>不完善</td>
</tr>
<tr>
<td>常见框架的适配</td>
<td>Servlet、Spring Cloud、Dubbo、gRPC  等</td>
<td>Servlet、Spring Cloud Netflix</td>
</tr>
</tbody></table>
<h3 id="Sentinel介绍"><a href="#Sentinel介绍" class="headerlink" title="Sentinel介绍"></a>Sentinel介绍</h3><p>Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址：<a target="_blank" rel="noopener" href="https://sentinelguard.io/zh-cn/index.html">https://sentinelguard.io/zh-cn/index.html</a></p>
<p>Sentinel 具有以下特征:</p>
<ul>
<li><strong>丰富的应用场景</strong>：Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景，例如秒杀（即突发流量控制在系统容量可以承受的范围）、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。</li>
<li><strong>完备的实时监控</strong>：Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据，甚至 500 台以下规模的集群的汇总运行情况。</li>
<li><strong>广泛的开源生态</strong>：Sentinel 提供开箱即用的与其它开源框架&#x2F;库的整合模块，例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。</li>
<li><strong>完善的</strong> <strong>SPI</strong> <strong>扩展点</strong>：Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。</li>
</ul>
<p>sentinel官方提供了UI控制台，方便我们对系统做限流设置，可以在<a target="_blank" rel="noopener" href="https://github.com/alibaba/Sentinel/releases">GitHub</a>下载。</p>
<p><strong>运行</strong></p>
<p>将jar包放到任意非中文目录，执行命令：</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -jar sentinel-dashboard-1.8.6.jar</span><br></pre></td></tr></table></figure>

<p>如果要修改Sentinel的默认端口、账户、密码，可以通过下列配置：</p>
<table>
<thead>
<tr>
<th><strong>配置项</strong></th>
<th><strong>默认值</strong></th>
<th><strong>说明</strong></th>
</tr>
</thead>
<tbody><tr>
<td>server.port</td>
<td>8080</td>
<td>服务端口</td>
</tr>
<tr>
<td>sentinel.dashboard.auth.username</td>
<td>sentinel</td>
<td>默认用户名</td>
</tr>
<tr>
<td>sentinel.dashboard.auth.password</td>
<td>sentinel</td>
<td>默认密码</td>
</tr>
</tbody></table>
<p>例如，修改端口：</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -Dserver.port=8090 -jar sentinel-dashboard-1.8.6.jar</span><br></pre></td></tr></table></figure>

<p><strong>访问</strong></p>
<p>访问<a href="http://localhost:8080页面，就可以看到sentinel的控制台了">http://localhost:8080页面，就可以看到sentinel的控制台了</a></p>
<p>需要输入账号和密码，默认都是：sentinel</p>
<p>登录后，发现一片空白，什么都没有，这是因为我们还没有与微服务整合。</p>
<h3 id="微服务整合Sentinel"><a href="#微服务整合Sentinel" class="headerlink" title="微服务整合Sentinel"></a>微服务整合Sentinel</h3><p>我们在order-service中整合sentinel，并连接sentinel的控制台，步骤如下：</p>
<p>1）引入sentinel依赖</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--sentinel--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.alibaba.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span> </span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-alibaba-sentinel<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>2）配置控制台</p>
<p>修改application.yaml文件，添加下面内容：</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line">  <span class="attr">port:</span> <span class="number">8088</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">cloud:</span> </span><br><span class="line">    <span class="attr">sentinel:</span></span><br><span class="line">      <span class="attr">transport:</span></span><br><span class="line">        <span class="attr">dashboard:</span> <span class="string">localhost:8080</span></span><br></pre></td></tr></table></figure>

<p>3）访问order-service的任意端点</p>
<p>打开浏览器，访问<a target="_blank" rel="noopener" href="http://localhost:8088/order/101%EF%BC%8C%E8%BF%99%E6%A0%B7%E6%89%8D%E8%83%BD%E8%A7%A6%E5%8F%91sentinel%E7%9A%84%E7%9B%91%E6%8E%A7%E3%80%82">http://localhost:8088/order/101，这样才能触发sentinel的监控。</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://localhost:8080/</span><br></pre></td></tr></table></figure>

<p>然后再访问sentinel的控制台，查看效果。</p>
<h2 id="流量控制"><a href="#流量控制" class="headerlink" title="流量控制"></a>流量控制</h2><p>限流是避免服务因突发的流量而发生故障，是对微服务雪崩问题的预防。</p>
<h3 id="簇点链路"><a href="#簇点链路" class="headerlink" title="簇点链路"></a>簇点链路</h3><p>当请求进入微服务时，首先会访问DispatcherServlet，然后进入Controller、Service、Mapper，这样的一个调用链就叫做<strong>簇点链路</strong>。簇点链路中被监控的每一个接口就是一个<strong>资源</strong>。</p>
<p>默认情况下sentinel会监控SpringMVC的每一个端点（Endpoint，也就是controller中的方法），因此SpringMVC的每一个端点（Endpoint）就是调用链路中的一个资源。</p>
<p>例如，我们刚才访问的order-service中的OrderController中的端点：&#x2F;order&#x2F;{orderId}</p>
<p>流控、熔断等都是针对簇点链路中的资源来设置的，因此我们可以点击对应资源后面的按钮来设置规则：</p>
<ul>
<li>流控：流量控制</li>
<li>降级：降级熔断</li>
<li>热点：热点参数限流，是限流的一种</li>
<li>授权：请求的权限控制</li>
</ul>
<h3 id="快速入门"><a href="#快速入门" class="headerlink" title="快速入门"></a>快速入门</h3><p>点击资源&#x2F;order&#x2F;{orderId}后面的流控按钮，就可以弹出表单。</p>
<blockquote>
<table>
<thead>
<tr>
<th>&#x2F;order&#x2F;{orderId}</th>
<th>0</th>
<th>0</th>
<th>0</th>
<th>0</th>
<th>0</th>
<th>0</th>
<th>流控  熔断  热点</th>
</tr>
</thead>
<tbody><tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
</blockquote>
<p>表单中可以填写限流规则</p>
<blockquote>
<p><strong>新增流控规则</strong></p>
<p><strong>资源名</strong></p>
<p><strong>针对来源</strong></p>
<p><strong>阈值类型</strong> ◉QPS    ○并发线程数    **单机阈值 **1</p>
<p><strong>是否集群</strong> ▢</p>
<p>​		高级选项</p>
</blockquote>
<p>其含义是限制 &#x2F;order&#x2F;{orderId}这个资源的单机QPS为1，即每秒只允许1次请求，超出的请求会被拦截并报错。</p>
<p><strong>练习</strong></p>
<p>需求：给 &#x2F;order&#x2F;{orderId}这个资源设置流控规则，QPS不能超过 5，然后测试。</p>
<p>1）首先在sentinel控制台添加限流规则</p>
<blockquote>
<p><strong>新增流控规则</strong></p>
<p><strong>资源名</strong></p>
<p><strong>针对来源</strong></p>
<p><strong>阈值类型</strong> ◉QPS    ○并发线程数    **单机阈值 **5</p>
<p><strong>是否集群</strong> ▢</p>
<p>​		高级选项</p>
</blockquote>
<p>2）利用jmeter测试</p>
<p><strong>Jmeter快速入门</strong>：</p>
<p>可以Apache Jmeter官网下载，地址：<a target="_blank" rel="noopener" href="http://jmeter.apache.org/download_jmeter.cgi">http://jmeter.apache.org/download_jmeter.cgi</a></p>
<p>因为下载的是zip包，解压缩即可使用</p>
<p>其中的bin目录就是执行的脚本，其中包含启动脚本</p>
<p>双击<code>jmeter.bat</code>即可运行，但是有两点注意：</p>
<ul>
<li>启动速度比较慢，要耐心等待</li>
<li>启动后黑窗口不能关闭，否则Jmeter也跟着关闭了</li>
</ul>
<p>默认Jmeter的语言是英文。</p>
<p>如果要永久中文，需要修改Jmeter的配置文件</p>
<p>打开jmeter文件夹，在bin目录中找到 <strong>jmeter.properties</strong>，添加下面配置：</p>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">language</span>=<span class="string">zh_CN</span></span><br></pre></td></tr></table></figure>

<p><strong>基本用法</strong></p>
<p>在测试计划上点鼠标右键，选择添加 &gt; 线程（用户） &gt; 线程组：</p>
<p>在新增的线程组中，填写线程信息：</p>
<blockquote>
<p>线程组</p>
<p>名称:<code>线程组</code></p>
<p>注释:</p>
<p>在取样器错误后要执行的动作<br>◉继续○启动下一进程循环○停止线程○停止测试○立即停止测试</p>
<p>线程属性</p>
<p>线程数:<code>100</code> &#x2F;&#x2F;代表用户数量</p>
<p>Ramp-Up时间（秒):<code>10</code>  &#x2F;&#x2F;代表用户访问时间，10秒。表示100个用户10秒内完成，那每秒就是10次</p>
<p>循环次数 ▢永远 <code>1</code>  &#x2F;&#x2F;每个用户访问次数，1就是只请求一次</p>
</blockquote>
<p>给线程组点鼠标右键，添加http取样器：</p>
<p>添加-&gt;取样器-&gt;HTTP请求</p>
<p>编写取样器内容：</p>
<blockquote>
<p>基本</p>
<p>Web服务器</p>
<p>协议：<code>http</code> &#x2F;&#x2F;请求协议 服务器名称或IP：<code>localhost</code> &#x2F;&#x2F;请求的域名或IP地址 端口：<code>8088</code> &#x2F;&#x2F;端口</p>
<p>HTTP请求</p>
<p><code>GET</code> 请求方式 路径：<code>/order/101</code> &#x2F;&#x2F;请求路径</p>
</blockquote>
<p>添加监听报告：</p>
<p>HTTP请求-&gt;添加-&gt;监听器-&gt;汇总报告</p>
<p>添加监听结果树：</p>
<p>HTTP请求-&gt;添加-&gt;监听器-&gt;查看结果树</p>
<h3 id="流控模式"><a href="#流控模式" class="headerlink" title="流控模式"></a>流控模式</h3><p>在添加限流规则时，点击高级选项，可以选择三种流控模式:</p>
<ul>
<li>直接:统计当前资源的请求，触发阈值时对当前资源直接限流，也是默认的模式</li>
<li>关联:统计与当前资源相关的另一个资源，触发阈值时，对当前资源限流</li>
<li>链路:统计从指定链路访问到本资源的请求，触发阈值时，对指定链路限流</li>
</ul>
<h4 id="关联模式"><a href="#关联模式" class="headerlink" title="关联模式"></a>关联模式</h4><p><strong>关联模式</strong>：统计与当前资源相关的另一个资源，触发阈值时，对当前资源限流</p>
<p><strong>配置规则</strong>：</p>
<blockquote>
<p>资源名 <code>/read</code></p>
<p>流控模式：<code>关联</code></p>
<p>关联资源：<code>/write</code></p>
<p>流控效果：<code>快速失败</code></p>
</blockquote>
<p><strong>语法说明</strong>：当&#x2F;write资源访问量触发阈值时，就会对&#x2F;read资源限流，避免影响&#x2F;write资源。</p>
<p><strong>使用场景</strong>：比如用户支付时需要修改订单状态，同时用户要查询订单。查询和修改操作会争抢数据库锁，产生竞争。业务需求是优先支付和更新订单的业务，因此当修改订单业务触发阈值时，需要对查询订单业务限流。</p>
<h4 id="链路模式"><a href="#链路模式" class="headerlink" title="链路模式"></a>链路模式</h4><p><strong>链路模式</strong>：只针对从指定链路访问到本资源的请求做统计，判断是否超过阈值。</p>
<p><strong>配置示例</strong>：</p>
<p>例如有两条请求链路：</p>
<ul>
<li>&#x2F;test1 –&gt; &#x2F;common</li>
<li>&#x2F;test2 –&gt; &#x2F;common</li>
</ul>
<p>如果只希望统计从&#x2F;test2进入到&#x2F;common的请求，则可以这样配置：</p>
<blockquote>
<p>资源名 <code>/common</code></p>
<p>流控模式：<code>链路</code></p>
<p>入口资源：<code>/test2</code></p>
</blockquote>
<p><strong>实战案例</strong></p>
<p>需求：有查询订单和创建订单业务，两者都需要查询商品。针对从查询订单进入到查询商品的请求统计，并设置限流。</p>
<p>步骤：</p>
<ol>
<li>在OrderService中添加一个queryGoods方法，不用实现业务</li>
<li>在OrderController中，改造&#x2F;order&#x2F;query端点，调用OrderService中的queryGoods方法</li>
<li>在OrderController中添加一个&#x2F;order&#x2F;save的端点，调用OrderService的queryGoods方法</li>
<li>给queryGoods设置限流规则，从&#x2F;order&#x2F;query进入queryGoods的方法限制QPS必须小于2</li>
</ol>
<p>实现：</p>
<p>1）添加查询商品方法</p>
<p>在order-service服务中，给OrderService类添加一个queryGoods方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">queryGoods</span><span class="params">()</span>&#123;</span><br><span class="line">    System.err.println(<span class="string">&quot;查询商品&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>2）查询订单时，查询商品</p>
<p>在order-service的OrderController中，修改&#x2F;order&#x2F;query端点的业务逻辑：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/query&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">queryOrder</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 查询商品</span></span><br><span class="line">    orderService.queryGoods();</span><br><span class="line">    <span class="comment">// 查询订单</span></span><br><span class="line">    System.out.println(<span class="string">&quot;查询订单&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;查询订单成功&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>3）新增订单，查询商品</p>
<p>在order-service的OrderController中，修改&#x2F;order&#x2F;save端点，模拟新增订单：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/save&quot;)</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">saveOrder</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 查询商品</span></span><br><span class="line">    orderService.queryGoods();</span><br><span class="line">    <span class="comment">// 查询订单</span></span><br><span class="line">    System.err.println(<span class="string">&quot;新增订单&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;新增订单成功&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>4）给查询商品添加资源标记</p>
<p>默认情况下，OrderService中的方法是不被Sentinel监控的，需要我们自己通过注解来标记要监控的方法。</p>
<p>给OrderService的queryGoods方法添加@SentinelResource注解：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SentinelResource(&quot;goods&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">queryGoods</span><span class="params">()</span>&#123;</span><br><span class="line">    System.err.println(<span class="string">&quot;查询商品&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>链路模式中，是对不同来源的两个链路做监控。但是sentinel默认会给进入SpringMVC的所有请求设置同一个root资源，会导致链路模式失效。</p>
<p>我们需要关闭这种对SpringMVC的资源聚合，修改order-service服务的application.yml文件：</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">cloud:</span></span><br><span class="line">    <span class="attr">sentinel:</span></span><br><span class="line">      <span class="attr">web-context-unify:</span> <span class="literal">false</span> <span class="comment"># 关闭context整合</span></span><br></pre></td></tr></table></figure>

<p>5）添加流控规则</p>
<p>点击goods资源后面的流控按钮，在弹出的表单中填写下面信息：</p>
<blockquote>
<p>资源名：<code>goods</code></p>
<p>流控模式：<code>链路</code></p>
<p>入口资源： <code>/order/query</code> &#x2F;&#x2F;只有从&#x2F;order&#x2F;query进入的资源需要被统计，限流</p>
</blockquote>
<p>只统计从&#x2F;order&#x2F;query进入&#x2F;goods的资源，QPS阈值为2，超出则被限流。</p>
<h3 id="流控效果"><a href="#流控效果" class="headerlink" title="流控效果"></a>流控效果</h3><p>在流控的高级选项中，还有一个流控效果选项</p>
<p>流控效果是指请求达到流控阈值时应该采取的措施，包括三种：</p>
<ul>
<li>快速失败：达到阈值后，新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。</li>
<li>warm up：预热模式，对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化，从一个较小值逐渐增加到最大阈值。</li>
<li>排队等待：让所有的请求按照先后次序排队执行，两个请求的间隔不能小于指定时长</li>
</ul>
<h4 id="warm-up"><a href="#warm-up" class="headerlink" title="warm up"></a>warm up</h4><p>阈值一般是一个微服务能承担的最大QPS，但是一个服务刚刚启动时，一切资源尚未初始化（<strong>冷启动</strong>），如果直接将QPS跑到最大值，可能导致服务瞬间宕机。</p>
<p>warm up也叫<strong>预热模式</strong>，是应对服务冷启动的一种方案。请求阈值初始值是 maxThreshold &#x2F; coldFactor，持续指定时长后，逐渐提高到maxThreshold值。而coldFactor的默认值是3.</p>
<p>例如，我设置QPS的maxThreshold为10，预热时间为5秒，那么初始阈值就是 10 &#x2F; 3 ，也就是3，然后在5秒后逐渐增长到10.</p>
<p><strong>案例</strong></p>
<p>需求：给&#x2F;order&#x2F;{orderId}这个资源设置限流，最大QPS为10，利用warm up效果，预热时长为5秒</p>
<p>1）配置流控规则：</p>
<blockquote>
<p>资源名：<code>/order/&#123;orderId&#125;</code> &#x2F;&#x2F;资源名</p>
<p>单机阈值：<code>10</code> &#x2F;&#x2F; QPS最大值</p>
<p>流控模式：<code>直接</code></p>
<p>流控效果：<code>Warm Up</code></p>
<p>预热时长：<code>5</code></p>
</blockquote>
<p>2）Jmeter测试</p>
<h4 id="排队等待"><a href="#排队等待" class="headerlink" title="排队等待"></a>排队等待</h4><p>当请求超过QPS阈值时，快速失败和warm up 会拒绝新的请求并抛出异常。</p>
<p>而排队等待则是让所有请求进入一个队列中，然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成，如果请求预期的等待时间超出最大时长，则会被拒绝。</p>
<p>工作原理</p>
<p>例如：QPS &#x3D; 5，意味着每200ms处理一个队列中的请求；timeout &#x3D; 2000，意味着<strong>预期等待时长</strong>超过2000ms的请求会被拒绝并抛出异常。</p>
<p>那什么叫做预期等待时长呢？</p>
<p>比如现在一下子来了12 个请求，因为每200ms执行一个请求，那么：</p>
<ul>
<li>第6个请求的<strong>预期等待时长</strong> &#x3D;  200 * （6 - 1） &#x3D; 1000ms</li>
<li>第12个请求的预期等待时长 &#x3D; 200 * （12-1） &#x3D; 2200ms</li>
</ul>
<p>现在，第1秒同时接收到10个请求，但第2秒只有1个请求，此时QPS的曲线这样的：</p>
<p><img src="/xlrblog/assets/image-20210716113147176.png" alt="image-20210716113147176"></p>
<p>如果使用队列模式做流控，所有进入的请求都要排队，以固定的200ms的间隔执行，QPS会变的很平滑：</p>
<p><img src="/xlrblog/assets/image-20210716113426524-1697900601549.png" alt="image-20210716113426524"></p>
<p>平滑的QPS曲线，对于服务器来说是更友好的。</p>
<p><strong>案例</strong></p>
<p>需求：给&#x2F;order&#x2F;{orderId}这个资源设置限流，最大QPS为10，利用排队的流控效果，超时时长设置为5s</p>
<p>1）添加流控规则</p>
<blockquote>
<p>资源名：<code>/order/&#123;orderId&#125;</code> &#x2F;&#x2F;资源名</p>
<p>单机阈值：<code>10</code> &#x2F;&#x2F; QPS最大值</p>
<p>流控模式：<code>直接</code></p>
<p>流控效果：<code>排队等待</code></p>
<p>超时时间：<code>5000</code> &#x2F;&#x2F;最长预期等待时间</p>
</blockquote>
<p>2）Jmeter测试</p>
<h3 id="热点参数限流"><a href="#热点参数限流" class="headerlink" title="热点参数限流"></a>热点参数限流</h3><p>之前的限流是统计访问某个资源的所有请求，判断是否超过QPS阈值。而热点参数限流是<strong>分别统计参数值相同的请求</strong>，判断是否超过QPS阈值。</p>
<h4 id="全局参数限流"><a href="#全局参数限流" class="headerlink" title="全局参数限流"></a>全局参数限流</h4><p>例如，一个根据id查询商品的接口</p>
<p>访问&#x2F;goods&#x2F;{id}的请求中，id参数值会有变化，热点参数限流会根据参数值分别统计QPS，统计结果</p>
<p>当id&#x3D;1的请求触发阈值被限流时，id值不为1的请求不受影响。</p>
<p>配置示例：</p>
<blockquote>
<p>资源名：<code>hot</code></p>
<p>限流模式：<code>QPS模式</code></p>
<p>参数索引：<code>0</code></p>
<p>单机阈值：<code>5</code>    统计窗口时长：<code>1</code>秒</p>
</blockquote>
<p>代表的含义是：对hot这个资源的0号参数（第一个参数）做统计，每1秒<strong>相同参数值</strong>的请求数不能超过5</p>
<h4 id="热点参数限流-1"><a href="#热点参数限流-1" class="headerlink" title="热点参数限流"></a>热点参数限流</h4><p>刚才的配置中，对查询商品这个接口的所有商品一视同仁，QPS都限定为5.</p>
<p>而在实际开发中，可能部分商品是热点商品，例如秒杀商品，我们希望这部分商品的QPS限制与其它商品不一样，高一些。那就需要配置热点参数限流的高级选项了</p>
<blockquote>
<p>​                                参数例外项<br>参数类型 <code>long</code><br>参数值 <code>例外项参数值</code> 限流阈值 <code>限流阈值</code>   十添加<br>参数值        参数类型    限流阈值        操作<br>100             long           10                    删除<br>101             long           15                    删除</p>
</blockquote>
<p>结合上一个配置，这里的含义是对0号的long类型参数限流，每1秒相同参数的QPS不能超过5，有两个例外：</p>
<ul>
<li>如果参数值是100，则每1秒允许的QPS为10</li>
<li>如果参数值是101，则每1秒允许的QPS为15</li>
</ul>
<h4 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h4><p><strong>案例需求</strong>：给&#x2F;order&#x2F;{orderId}这个资源添加热点参数限流，规则如下：</p>
<ul>
<li>默认的热点参数规则是每1秒请求量不超过2</li>
<li>给102这个参数设置例外：每1秒请求量不超过4</li>
<li>给103这个参数设置例外：每1秒请求量不超过10</li>
</ul>
<blockquote>
<p><strong>注意事项</strong>：热点参数限流对默认的SpringMVC资源无效，需要利用@SentinelResource注解标记资源</p>
</blockquote>
<h5 id="1）标记资源"><a href="#1）标记资源" class="headerlink" title="1）标记资源"></a>1）标记资源</h5><p>给order-service中的OrderController中的&#x2F;order&#x2F;{orderId}资源添加注解</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;order&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OrderController</span> &#123;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@Autowired</span></span><br><span class="line">   <span class="keyword">private</span> OrderService orderService;</span><br><span class="line"></span><br><span class="line">   <span class="meta">@SentinelResource(&quot;hot&quot;)</span></span><br><span class="line">    <span class="meta">@GetMapping(&quot;&#123;orderId&#125;&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> Order <span class="title function_">queryOrderByUserId</span><span class="params">(<span class="meta">@PathVariable(&quot;orderId&quot;)</span> Long orderId)</span> &#123;</span><br><span class="line">        <span class="comment">// 根据id查询订单并返回</span></span><br><span class="line">        <span class="keyword">return</span> orderService.queryOrderById(orderId);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<h5 id="2）热点参数限流规则"><a href="#2）热点参数限流规则" class="headerlink" title="2）热点参数限流规则"></a>2）热点参数限流规则</h5><p>访问该接口，可以看到我们标记的hot资源出现了</p>
<p>点击左侧菜单中<strong>热点规则</strong>菜单</p>
<p>点击新增，填写表单：</p>
<blockquote>
<p>资源名：<code>hot</code></p>
<p>限流模式：<code>QPS模式</code></p>
<p>参数索引：<code>0</code> &#x2F;&#x2F; 参数索引，只有一个参数，所以为0</p>
<p>单机阈值：<code>2</code>    统计窗口时长：<code>1</code>秒</p>
<p>​                                参数例外项<br>参数类型 <code>long</code> &#x2F;&#x2F;只能基本数据类型、String类型<br>参数值 <code>例外项参数值</code> 限流阈值 <code>限流阈值</code>   十添加<br>参数值        参数类型    限流阈值        操作<br>100             long           4                      删除<br>101             long           10                    删除</p>
</blockquote>
<h5 id="3）Jmeter测试"><a href="#3）Jmeter测试" class="headerlink" title="3）Jmeter测试"></a>3）Jmeter测试</h5><h2 id="隔离和降级"><a href="#隔离和降级" class="headerlink" title="隔离和降级"></a>隔离和降级</h2><p>限流是一种预防措施，虽然限流可以尽量避免因高并发而引起的服务故障，但服务还会因为其它原因而故障。</p>
<p>而要将这些故障控制在一定范围，避免雪崩，就要靠<strong>线程隔离</strong>（舱壁模式）和<strong>熔断降级</strong>手段了。</p>
<p><strong>线程隔离</strong>之前讲到过：调用者在调用服务提供者时，给每个调用的请求分配独立线程池，出现故障时，最多消耗这个线程池内资源，避免把调用者的所有资源耗尽。</p>
<p><strong>熔断降级</strong>：是在调用方这边加入断路器，统计对服务提供者的调用，如果调用的失败比例过高，则熔断该业务，不允许访问该服务的提供者了。</p>
<p>可以看到，不管是线程隔离还是熔断降级，都是对<strong>客户端</strong>（调用方）的保护。需要在<strong>调用方</strong> 发起远程调用时做线程隔离、或者服务熔断。</p>
<p>而我们的微服务远程调用都是基于Feign来完成的，因此我们需要将Feign与Sentinel整合，在Feign里面实现线程隔离和服务熔断。</p>
<h3 id="FeignClient整合Sentinel"><a href="#FeignClient整合Sentinel" class="headerlink" title="FeignClient整合Sentinel"></a>FeignClient整合Sentinel</h3><p>SpringCloud中，微服务调用都是通过Feign来实现的，因此做客户端保护必须整合Feign和Sentinel。</p>
<h4 id="修改配置，开启sentinel功能"><a href="#修改配置，开启sentinel功能" class="headerlink" title="修改配置，开启sentinel功能"></a>修改配置，开启sentinel功能</h4><p>修改OrderService的application.yml文件，开启Feign的Sentinel功能：</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">feign:</span></span><br><span class="line">  <span class="attr">sentinel:</span></span><br><span class="line">    <span class="attr">enabled:</span> <span class="literal">true</span> <span class="comment"># 开启feign对sentinel的支持</span></span><br></pre></td></tr></table></figure>

<h4 id="编写失败降级逻辑"><a href="#编写失败降级逻辑" class="headerlink" title="编写失败降级逻辑"></a>编写失败降级逻辑</h4><p>业务失败后，不能直接报错，而应该返回用户一个友好提示或者默认结果，这个就是失败降级逻辑。</p>
<p>给FeignClient编写失败后的降级逻辑</p>
<p>①方式一：FallbackClass，无法对远程调用的异常做处理</p>
<p>②方式二：FallbackFactory，可以对远程调用的异常做处理，我们选择这种</p>
<p>这里我们演示方式二的失败降级处理。</p>
<p><strong>步骤一</strong>：在feing-api项目中定义类，实现FallbackFactory：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.itcast.feign.clients.fallback;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.itcast.feign.clients.UserClient;</span><br><span class="line"><span class="keyword">import</span> cn.itcast.feign.pojo.User;</span><br><span class="line"><span class="keyword">import</span> feign.hystrix.FallbackFactory;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserClientFallbackFactory</span> <span class="keyword">implements</span> <span class="title class_">FallbackFactory</span>&lt;UserClient&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> UserClient <span class="title function_">create</span><span class="params">(Throwable throwable)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">UserClient</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> User <span class="title function_">findById</span><span class="params">(Long id)</span> &#123;</span><br><span class="line">                log.error(<span class="string">&quot;查询用户异常&quot;</span>,throwable);</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>步骤二</strong>：在feing-api项目中的DefaultFeignConfiguration类中将UserClientFallbackFactory注册为一个Bean：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.itcast.feign.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.itcast.feign.clients.fallback.UserClientFallbackFactory;</span><br><span class="line"><span class="keyword">import</span> feign.Logger;</span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Bean;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultFeignConfiguration</span>  &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> Logger.Level <span class="title function_">feignLogLevel</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> Logger.Level.BASIC; <span class="comment">// 日志级别为BASIC</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> UserClientFallbackFactory <span class="title function_">userClientFallbackFactory</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">UserClientFallbackFactory</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>步骤三</strong>：在feing-api项目中的UserClient接口中使用UserClientFallbackFactory</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.itcast.feign.clients;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.itcast.feign.clients.fallback.UserClientFallbackFactory;</span><br><span class="line"><span class="keyword">import</span> cn.itcast.feign.pojo.User;</span><br><span class="line"><span class="keyword">import</span> org.springframework.cloud.openfeign.FeignClient;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.GetMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.PathVariable;</span><br><span class="line"></span><br><span class="line"><span class="meta">@FeignClient(value = &quot;userservice&quot;,fallbackFactory = UserClientFallbackFactory.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserClient</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping(&quot;/user/&#123;id&#125;&quot;)</span></span><br><span class="line">    User <span class="title function_">findById</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>重启后，访问一次订单查询业务，然后查看sentinel控制台，可以看到新的簇点链路</p>
<h3 id="线程隔离（舱壁模式"><a href="#线程隔离（舱壁模式" class="headerlink" title="线程隔离（舱壁模式)"></a>线程隔离（舱壁模式)</h3><h4 id="线程隔离的实现方式"><a href="#线程隔离的实现方式" class="headerlink" title="线程隔离的实现方式"></a>线程隔离的实现方式</h4><p>线程隔离有两种方式实现：</p>
<ul>
<li>线程池隔离</li>
<li>信号量隔离（Sentinel默认采用）</li>
</ul>
<p><strong>线程池隔离</strong>：给每个服务调用业务分配一个线程池，利用线程池本身实现隔离效果</p>
<ul>
<li>优点：支持主动超时；支持异步调用 </li>
<li>缺点：线程的额外开销比较大 </li>
<li>场景：低扇出</li>
</ul>
<p><strong>信号量隔离</strong>：不创建线程池，而是计数器模式，记录业务使用的线程数量，达到信号量上限时，禁止新的请求。</p>
<ul>
<li>优点：轻量级，无额外开销</li>
<li>缺点：不支持主动超时；不支持异步调用 </li>
<li>场景：高频调用；高扇出</li>
</ul>
<h4 id="sentinel的线程隔离"><a href="#sentinel的线程隔离" class="headerlink" title="sentinel的线程隔离"></a>sentinel的线程隔离</h4><p><strong>用法说明</strong>：</p>
<p>在添加限流规则时，可以选择两种阈值类型：</p>
<ul>
<li>QPS：就是每秒的请求数，在快速入门中已经演示过</li>
<li>线程数：是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量，实现<strong>线程隔离</strong>（舱壁模式）。</li>
</ul>
<p><strong>案例需求</strong>：给 order-service服务中的UserClient的查询用户接口设置流控规则，线程数不能超过 2。然后利用jemeter测试。</p>
<p>1）配置隔离规则</p>
<p>选择feign接口后面的流控按钮</p>
<p>填写表单</p>
<blockquote>
<p>资源名：<code>GET:http://userservice/user/&#123;id&#125;</code></p>
<p>针对来源：<code>default</code></p>
<p>阈值类型：<code>并发线程数</code> &#x2F;&#x2F;这个就是信号量隔离    单机阈值：<code>2</code></p>
</blockquote>
<p>2）Jmeter测试</p>
<h3 id="熔断降级"><a href="#熔断降级" class="headerlink" title="熔断降级"></a>熔断降级</h3><p>熔断降级是解决雪崩问题的重要手段。其思路是由<strong>断路器</strong>统计服务调用的异常比例、慢请求比例，如果超出阈值则会<strong>熔断</strong>该服务。即拦截访问该服务的一切请求；而当服务恢复时，断路器会放行访问该服务的请求。</p>
<p>断路器控制熔断和放行是通过状态机来完成的</p>
<p>状态机包括三个状态：</p>
<ul>
<li>closed：关闭状态，断路器放行所有请求，并开始统计异常比例、慢请求比例。超过阈值则切换到open状态</li>
<li>open：打开状态，服务调用被<strong>熔断</strong>，访问被熔断服务的请求会被拒绝，快速失败，直接走降级逻辑。Open状态5秒后会进入half-open状态</li>
<li>half-open：半开状态，放行一次请求，根据执行结果来判断接下来的操作。<ul>
<li>请求成功：则切换到closed状态</li>
<li>请求失败：则切换到open状态</li>
</ul>
</li>
</ul>
<p>断路器熔断策略有三种：慢调用、异常比例、异常数</p>
<h4 id="慢调用"><a href="#慢调用" class="headerlink" title="慢调用"></a>慢调用</h4><p><strong>慢调用</strong>：业务的响应时长（RT）大于指定时长的请求认定为慢调用请求。在指定时间内，如果请求数量超过设定的最小数量，慢调用比例大于设定的阈值，则触发熔断。</p>
<p><strong>慢调用</strong>：业务的响应时长（RT）大于指定时长的请求认定为慢调用请求。在指定时间内，如果请求数量超过设定的最小数量，慢调用比例大于设定的阈值，则触发熔断。</p>
<p>例如：</p>
<blockquote>
<p>新增降级规则<br>资源名 <code>/test</code><br>熔断策略 ◉慢调用比例 O异常比例 O异常数<br>最大RT <code>500</code> ms        比例阈值 <code>0.5</code><br>熔断时长 <code>5</code> s        最小请求数 <code>10</code><br>统计时长<code>10000</code>ms</p>
</blockquote>
<p>解读：RT超过500ms的调用是慢调用，统计最近10000ms内的请求，如果请求量超过10次，并且慢调用比例不低于0.5，则触发熔断，熔断时长为5秒。然后进入half-open状态，放行一次请求做测试。</p>
<p><strong>案例</strong></p>
<p>需求：给 UserClient的查询用户接口设置降级规则，慢调用的RT阈值为50ms，统计时间为1秒，最小请求数量为5，失败阈值比例为0.4，熔断时长为5</p>
<p>1）设置慢调用</p>
<p>修改user-service中的&#x2F;user&#x2F;{id}这个接口的业务。通过休眠模拟一个延迟时间：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;/&#123;id&#125;&quot;)</span></span><br><span class="line"><span class="keyword">public</span> User <span class="title function_">queryById</span><span class="params">(<span class="meta">@PathVariable(&quot;id&quot;)</span> Long id)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="keyword">if</span> (id == <span class="number">1</span>) &#123;</span><br><span class="line">        Thread.sleep(<span class="number">60</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> userService.queryById(id);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>此时，orderId&#x3D;101的订单，关联的是id为1的用户，调用时长为60ms:</p>
<p>orderId&#x3D;102的订单，关联的是id为2的用户，调用时长为非常短；</p>
<p>2）设置熔断规则</p>
<p>下面，给feign接口设置降级规则：</p>
<blockquote>
<table>
<thead>
<tr>
<th>GET:<a target="_blank" rel="noopener" href="http://userservice/user/%7Bid%7D">http://userservice/user/{id}</a></th>
<th>0</th>
<th>0</th>
<th>0</th>
<th>0</th>
<th>0</th>
<th>0</th>
<th>+流控  +熔断  +热点</th>
</tr>
</thead>
<tbody><tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
</blockquote>
<p>选择+熔断</p>
<blockquote>
<p>资源名 <code>GET:http:/luserserviceluserl&#123;id&#125;</code><br>熔断策略 ◉慢调用比例 O异常比例 O异常数<br>最大RT <code>50</code>    比例阈值 <code>0.4</code><br>熔断时长 <code>5</code> 最小请求数 <code>5</code><br>统计时长 <code>1000</code>ms</p>
</blockquote>
<p>超过50ms的请求都会被认为是慢请求</p>
<p>3）测试</p>
<p>在浏览器访问：<a target="_blank" rel="noopener" href="http://localhost:8088/order/101%EF%BC%8C%E5%BF%AB%E9%80%9F%E5%88%B7%E6%96%B05%E6%AC%A1%EF%BC%8C%E5%8F%AF%E4%BB%A5%E5%8F%91%E7%8E%B0%EF%BC%9A">http://localhost:8088/order/101，快速刷新5次，可以发现：</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;id&quot;:101,&quot;price&quot;:699900,&quot;name&quot;:&quot;Apple 苹果 iPhone 12 &quot;,&quot;num&quot;:1,&quot;userId&quot;:1,&quot;user&quot;:&#123;&quot;id&quot;:null,&quot;username&quot;:null,&quot;address&quot;:null&#125;&#125;</span><br></pre></td></tr></table></figure>

<p>触发了熔断，请求时长缩短至5ms，快速失败了，并且走降级逻辑，返回的null</p>
<p>在浏览器访问：<a target="_blank" rel="noopener" href="http://localhost:8088/order/102%EF%BC%8C%E7%AB%9F%E7%84%B6%E4%B9%9F%E8%A2%AB%E7%86%94%E6%96%AD%E4%BA%86%EF%BC%9A">http://localhost:8088/order/102，竟然也被熔断了：</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;id&quot;:102,&quot;price&quot;:209900,&quot;name&quot;:&quot;雅迪 yadea 新国标电动车&quot;,&quot;num&quot;:1,&quot;userId&quot;:2,&quot;user&quot;:&#123;&quot;id&quot;:null,&quot;username&quot;:null,&quot;address&quot;:null&#125;&#125;</span><br></pre></td></tr></table></figure>

<h4 id="异常比例、异常数"><a href="#异常比例、异常数" class="headerlink" title="异常比例、异常数"></a>异常比例、异常数</h4><p><strong>异常比例或异常数</strong>：统计指定时间内的调用，如果调用次数超过指定请求数，并且出现异常的比例达到设定的比例阈值（或超过指定异常数），则触发熔断。</p>
<p>例如，一个异常比例设置：</p>
<blockquote>
<p>资源名 <code>/test</code></p>
<p>熔断策略 O慢调用比例  ◉ 异常数  O异常数</p>
<p>比例阈值 <code>0.4</code></p>
<p>熔断时长 <code>5</code> s        最小请求数 <code>10</code></p>
<p>统计时长 <code>1000</code>ms</p>
</blockquote>
<p>解读：统计最近1000ms内的请求，如果请求量超过10次，并且异常比例不低于0.4，则触发熔断。</p>
<p>一个异常数设置：</p>
<blockquote>
<p>资源名 <code>/test</code></p>
<p>熔断策略 O慢调用比例   O异常数  ◉异常数</p>
<p>异常数 <code>2</code></p>
<p>熔断时长 <code>5</code> s        最小请求数 <code>10</code></p>
<p>统计时长 <code>1000</code>ms</p>
</blockquote>
<p>解读：统计最近1000ms内的请求，如果请求量超过10次，并且异常比例不低于2次，则触发熔断。</p>
<p><strong>案例</strong></p>
<p>需求：给 UserClient的查询用户接口设置降级规则，统计时间为1秒，最小请求数量为5，失败阈值比例为0.4，熔断时长为5s</p>
<p>1）设置异常请求</p>
<p>首先，修改user-service中的&#x2F;user&#x2F;{id}这个接口的业务。手动抛出异常，以触发异常比例的熔断</p>
<p>也就是说，id 为 2时，就会触发异常</p>
<p>2）设置熔断规则</p>
<p>下面，给feign接口设置降级规则：</p>
<blockquote>
<p>资源名 <code>GET:http://userservice/user/&#123;id&#125;</code></p>
<p>熔断策略 O慢调用比例  ◉ 异常数  O异常数</p>
<p>比例阈值 <code>0.4</code></p>
<p>熔断时长 <code>5</code> s        最小请求数 <code>5</code></p>
<p>统计时长 <code>1000</code>ms</p>
</blockquote>
<p>在5次请求中，只要异常比例超过0.4，也就是有2次以上的异常，就会触发熔断。</p>
<p>3）测试</p>
<p>在浏览器快速访问：<a target="_blank" rel="noopener" href="http://localhost:8088/order/102%EF%BC%8C%E5%BF%AB%E9%80%9F%E5%88%B7%E6%96%B05%E6%AC%A1%EF%BC%8C%E8%A7%A6%E5%8F%91%E7%86%94%E6%96%AD">http://localhost:8088/order/102，快速刷新5次，触发熔断</a></p>
<p>此时，我们去访问本来应该正常的103，也发生熔断</p>
<h2 id="授权规则"><a href="#授权规则" class="headerlink" title="授权规则"></a>授权规则</h2><h3 id="授权规则-1"><a href="#授权规则-1" class="headerlink" title="授权规则"></a>授权规则</h3><h4 id="基本规则"><a href="#基本规则" class="headerlink" title="基本规则"></a>基本规则</h4><p>授权规则可以对调用方的来源做控制，有白名单和黑名单两种方式。</p>
<ul>
<li>白名单：来源（origin）在白名单内的调用者允许访问</li>
<li>黑名单：来源（origin）在黑名单内的调用者不允许访问</li>
</ul>
<p>点击左侧菜单的授权，可以看到授权规则</p>
<blockquote>
<p>资源名：资源名称</p>
<p>流控应用：指调用方，多个调用方名称用半角英文逗号(,)隔开</p>
<p>授权类型 ◉白名单    O黑名单</p>
</blockquote>
<ul>
<li>资源名：就是受保护的资源，例如&#x2F;order&#x2F;{orderId}</li>
<li>流控应用：是来源者的名单，<ul>
<li>如果是勾选白名单，则名单中的来源被许可访问。</li>
<li>如果是勾选黑名单，则名单中的来源被禁止访问。</li>
</ul>
</li>
</ul>
<p>比如：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">gateway-----------&gt; +-------------+</span><br><span class="line">                    |order-service|</span><br><span class="line">浏览器-------------&gt; +-------------+</span><br></pre></td></tr></table></figure>

<p>我们允许请求从gateway到order-service，不允许浏览器访问order-service，那么白名单中就要填写<strong>网关的来源名称（origin）</strong>。</p>
<h4 id="如何获取origin"><a href="#如何获取origin" class="headerlink" title="如何获取origin"></a>如何获取origin</h4><p>Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">RequestOriginParser</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 从请求request对象中获取origin，获取方式自定义</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    String <span class="title function_">parseOrigin</span><span class="params">(HttpServletRequest request)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个方法的作用就是从request对象中，获取请求者的origin值并返回。</p>
<p>默认情况下，sentinel不管请求者从哪里来，返回值永远是default，也就是说一切请求的来源都被认为是一样的值default。</p>
<p>因此，我们需要自定义这个接口的实现，让<strong>不同的请求，返回不同的origin</strong>。</p>
<p>例如order-service服务中，我们定义一个RequestOriginParser的实现类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.itcast.order.sentinel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"><span class="keyword">import</span> org.springframework.util.StringUtils;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletRequest;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HeaderOriginParser</span> <span class="keyword">implements</span> <span class="title class_">RequestOriginParser</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">parseOrigin</span><span class="params">(HttpServletRequest request)</span> &#123;</span><br><span class="line">        <span class="comment">// 1.获取请求头</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">origin</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;origin&quot;</span>);</span><br><span class="line">        <span class="comment">// 2.非空判断</span></span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isEmpty(origin)) &#123;</span><br><span class="line">            origin = <span class="string">&quot;blank&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> origin;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们会尝试从request-header中获取origin值。</p>
<h4 id="给网关添加请求头"><a href="#给网关添加请求头" class="headerlink" title="给网关添加请求头"></a>给网关添加请求头</h4><p>既然获取请求origin的方式是从reques-header中获取origin值，我们必须让<strong>所有从gateway路由到微服务的请求都带上origin头</strong>。</p>
<p>这个需要利用之前学习的一个GatewayFilter来实现，AddRequestHeaderGatewayFilter。</p>
<p>修改gateway服务中的application.yml，添加一个defaultFilter：</p>
<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">cloud:</span></span><br><span class="line">    <span class="attr">gateway:</span></span><br><span class="line">      <span class="attr">default-filters:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="string">AddRequestHeader=origin,gateway</span></span><br><span class="line">      <span class="attr">routes:</span></span><br><span class="line">       <span class="comment"># ...略</span></span><br></pre></td></tr></table></figure>

<p>这样，从gateway路由的所有请求都会带上origin头，值为gateway。而从其它地方到达微服务的请求则没有这个头。</p>
<h4 id="配置授权规则"><a href="#配置授权规则" class="headerlink" title="配置授权规则"></a>配置授权规则</h4><p>接下来，我们添加一个授权规则，放行origin值为gateway的请求。</p>
<p>配置如下：</p>
<p>资源名：<code>/order/&#123;orderId&#125;</code></p>
<p>流控应用：<code>gateway</code></p>
<p>授权类型 ◉白名单    O黑名单</p>
<p>现在，我们直接跳过网关，访问order-service服务：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://localhost:8088/order/101</span><br></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Blocked by Sentinel (flow limiting)</span><br></pre></td></tr></table></figure>

<p>通过网关访问：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://localhost:10010/order/101?authorization=admin</span><br></pre></td></tr></table></figure>

<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;id&quot;:101,&quot;price&quot;:699900,&quot;name&quot;:&quot;Apple 苹果 iPhone 12 &quot;,&quot;num&quot;:1,&quot;userId&quot;:1,&quot;user&quot;:&#123;&quot;id&quot;:1,&quot;username&quot;:&quot;杨柳&quot;,&quot;address&quot;:&quot;湖南省衡阳市&quot;&#125;&#125;</span><br></pre></td></tr></table></figure>

<h3 id="自定义异常结果"><a href="#自定义异常结果" class="headerlink" title="自定义异常结果"></a>自定义异常结果</h3><p>默认情况下，发生限流、降级、授权拦截时，都会抛出异常到调用方。异常结果都是flow limmiting（限流）。这样不够友好，无法得知是限流还是降级还是授权拦截。</p>
<h4 id="异常类型"><a href="#异常类型" class="headerlink" title="异常类型"></a>异常类型</h4><p>而如果要自定义异常时的返回结果，需要实现BlockExceptionHandler接口：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">BlockExceptionHandler</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 处理请求被限流、降级、授权拦截时抛出的异常：BlockException</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">handle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, BlockException e)</span> <span class="keyword">throws</span> Exception;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个方法有三个参数：</p>
<ul>
<li>HttpServletRequest request：request对象</li>
<li>HttpServletResponse response：response对象</li>
<li>BlockException e：被sentinel拦截时抛出的异常</li>
</ul>
<p>这里的BlockException包含多个不同的子类：</p>
<table>
<thead>
<tr>
<th><strong>异常</strong></th>
<th><strong>说明</strong></th>
</tr>
</thead>
<tbody><tr>
<td>FlowException</td>
<td>限流异常</td>
</tr>
<tr>
<td>ParamFlowException</td>
<td>热点参数限流的异常</td>
</tr>
<tr>
<td>DegradeException</td>
<td>降级异常</td>
</tr>
<tr>
<td>AuthorityException</td>
<td>授权规则异常</td>
</tr>
<tr>
<td>SystemBlockException</td>
<td>系统规则异常</td>
</tr>
</tbody></table>
<h4 id="自定义异常处理"><a href="#自定义异常处理" class="headerlink" title="自定义异常处理"></a>自定义异常处理</h4><p>下面，我们就在order-service定义一个自定义异常处理类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.itcast.order.sentinel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.csp.sentinel.slots.block.BlockException;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.csp.sentinel.slots.block.flow.FlowException;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Component;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletRequest;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletResponse;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SentinelExceptionHandler</span> <span class="keyword">implements</span> <span class="title class_">BlockExceptionHandler</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, BlockException e)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">msg</span> <span class="operator">=</span> <span class="string">&quot;未知异常&quot;</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">status</span> <span class="operator">=</span> <span class="number">429</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (e <span class="keyword">instanceof</span> FlowException) &#123;</span><br><span class="line">            msg = <span class="string">&quot;请求被限流了&quot;</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> ParamFlowException) &#123;</span><br><span class="line">            msg = <span class="string">&quot;请求被热点参数限流&quot;</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> DegradeException) &#123;</span><br><span class="line">            msg = <span class="string">&quot;请求被降级了&quot;</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (e <span class="keyword">instanceof</span> AuthorityException) &#123;</span><br><span class="line">            msg = <span class="string">&quot;没有权限访问&quot;</span>;</span><br><span class="line">            status = <span class="number">401</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        response.setContentType(<span class="string">&quot;application/json;charset=utf-8&quot;</span>);</span><br><span class="line">        response.setStatus(status);</span><br><span class="line">        response.getWriter().println(<span class="string">&quot;&#123;\&quot;msg\&quot;: &quot;</span> + msg + <span class="string">&quot;, \&quot;status\&quot;: &quot;</span> + status + <span class="string">&quot;&#125;&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>重启测试，在不同场景下，会返回不同的异常消息.</p>
<p>限流：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;msg&quot;: 请求被限流了, &quot;status&quot;: 429&#125;</span><br></pre></td></tr></table></figure>

<p>授权拦截时：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;&quot;msg&quot;: 没有权限访问, &quot;status&quot;: 401&#125;</span><br></pre></td></tr></table></figure>

<h2 id="规则持久化"><a href="#规则持久化" class="headerlink" title="规则持久化"></a>规则持久化</h2><p>现在，sentinel的所有规则都是内存存储，重启后所有规则都会丢失。在生产环境下，我们必须确保这些规则的持久化，避免丢失。</p>
<h3 id="规则管理模式"><a href="#规则管理模式" class="headerlink" title="规则管理模式"></a>规则管理模式</h3><p>规则是否能持久化，取决于规则管理模式，sentinel支持三种规则管理模式：</p>
<ul>
<li>原始模式：Sentinel的默认模式，将规则保存在内存，重启服务会丢失。</li>
<li>pull模式</li>
<li>push模式</li>
</ul>
<h4 id="pull模式"><a href="#pull模式" class="headerlink" title="pull模式"></a>pull模式</h4><p>pull模式：控制台将配置的规则推送到Sentinel客户端，而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询，更新本地规则。</p>
<h4 id="push模式"><a href="#push模式" class="headerlink" title="push模式"></a>push模式</h4><p>push模式：控制台将配置规则推送到远程配置中心，例如Nacos。Sentinel客户端监听Nacos，获取配置变更的推送消息，完成本地配置更新。</p>
<h3 id="实现push模式"><a href="#实现push模式" class="headerlink" title="实现push模式"></a>实现push模式</h3><p>……</p>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/xlrblog/tags/SpringCloud/" rel="tag"># SpringCloud</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/xlrblog/2023/10/18/RabbitMQ%E5%AD%A6%E4%B9%A0/" rel="prev" title="RabbitMQ学习">
      <i class="fa fa-chevron-left"></i> RabbitMQ学习
    </a></div>
      <div class="post-nav-item">
    <a href="/xlrblog/2023/10/29/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/" rel="next" title="分布式事务">
      分布式事务 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%88%9D%E8%AF%86Sentinel"><span class="nav-number">1.</span> <span class="nav-text">初识Sentinel</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%9B%AA%E5%B4%A9%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88"><span class="nav-number">1.1.</span> <span class="nav-text">雪崩问题及解决方案</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%9B%AA%E5%B4%A9%E9%97%AE%E9%A2%98"><span class="nav-number">1.1.1.</span> <span class="nav-text">雪崩问题</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%B6%85%E6%97%B6%E5%A4%84%E7%90%86"><span class="nav-number">1.1.2.</span> <span class="nav-text">超时处理</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E4%BB%93%E5%A3%81%E6%A8%A1%E5%BC%8F"><span class="nav-number">1.1.3.</span> <span class="nav-text">仓壁模式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%96%AD%E8%B7%AF%E5%99%A8"><span class="nav-number">1.1.4.</span> <span class="nav-text">断路器</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%99%90%E6%B5%81"><span class="nav-number">1.1.5.</span> <span class="nav-text">限流</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%80%BB%E7%BB%93"><span class="nav-number">1.1.6.</span> <span class="nav-text">总结</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4%E6%8A%80%E6%9C%AF%E5%AF%B9%E6%AF%94"><span class="nav-number">1.2.</span> <span class="nav-text">服务保护技术对比</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Sentinel%E4%BB%8B%E7%BB%8D"><span class="nav-number">1.3.</span> <span class="nav-text">Sentinel介绍</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%95%B4%E5%90%88Sentinel"><span class="nav-number">1.4.</span> <span class="nav-text">微服务整合Sentinel</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6"><span class="nav-number">2.</span> <span class="nav-text">流量控制</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%B0%87%E7%82%B9%E9%93%BE%E8%B7%AF"><span class="nav-number">2.1.</span> <span class="nav-text">簇点链路</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8"><span class="nav-number">2.2.</span> <span class="nav-text">快速入门</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%B5%81%E6%8E%A7%E6%A8%A1%E5%BC%8F"><span class="nav-number">2.3.</span> <span class="nav-text">流控模式</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%85%B3%E8%81%94%E6%A8%A1%E5%BC%8F"><span class="nav-number">2.3.1.</span> <span class="nav-text">关联模式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%93%BE%E8%B7%AF%E6%A8%A1%E5%BC%8F"><span class="nav-number">2.3.2.</span> <span class="nav-text">链路模式</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%B5%81%E6%8E%A7%E6%95%88%E6%9E%9C"><span class="nav-number">2.4.</span> <span class="nav-text">流控效果</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#warm-up"><span class="nav-number">2.4.1.</span> <span class="nav-text">warm up</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%8E%92%E9%98%9F%E7%AD%89%E5%BE%85"><span class="nav-number">2.4.2.</span> <span class="nav-text">排队等待</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81"><span class="nav-number">2.5.</span> <span class="nav-text">热点参数限流</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%85%A8%E5%B1%80%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81"><span class="nav-number">2.5.1.</span> <span class="nav-text">全局参数限流</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81-1"><span class="nav-number">2.5.2.</span> <span class="nav-text">热点参数限流</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%A1%88%E4%BE%8B"><span class="nav-number">2.5.3.</span> <span class="nav-text">案例</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#1%EF%BC%89%E6%A0%87%E8%AE%B0%E8%B5%84%E6%BA%90"><span class="nav-number">2.5.3.1.</span> <span class="nav-text">1）标记资源</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#2%EF%BC%89%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81%E8%A7%84%E5%88%99"><span class="nav-number">2.5.3.2.</span> <span class="nav-text">2）热点参数限流规则</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#3%EF%BC%89Jmeter%E6%B5%8B%E8%AF%95"><span class="nav-number">2.5.3.3.</span> <span class="nav-text">3）Jmeter测试</span></a></li></ol></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E9%9A%94%E7%A6%BB%E5%92%8C%E9%99%8D%E7%BA%A7"><span class="nav-number">3.</span> <span class="nav-text">隔离和降级</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#FeignClient%E6%95%B4%E5%90%88Sentinel"><span class="nav-number">3.1.</span> <span class="nav-text">FeignClient整合Sentinel</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E4%BF%AE%E6%94%B9%E9%85%8D%E7%BD%AE%EF%BC%8C%E5%BC%80%E5%90%AFsentinel%E5%8A%9F%E8%83%BD"><span class="nav-number">3.1.1.</span> <span class="nav-text">修改配置，开启sentinel功能</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%BC%96%E5%86%99%E5%A4%B1%E8%B4%A5%E9%99%8D%E7%BA%A7%E9%80%BB%E8%BE%91"><span class="nav-number">3.1.2.</span> <span class="nav-text">编写失败降级逻辑</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%BA%BF%E7%A8%8B%E9%9A%94%E7%A6%BB%EF%BC%88%E8%88%B1%E5%A3%81%E6%A8%A1%E5%BC%8F"><span class="nav-number">3.2.</span> <span class="nav-text">线程隔离（舱壁模式)</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%BA%BF%E7%A8%8B%E9%9A%94%E7%A6%BB%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F"><span class="nav-number">3.2.1.</span> <span class="nav-text">线程隔离的实现方式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#sentinel%E7%9A%84%E7%BA%BF%E7%A8%8B%E9%9A%94%E7%A6%BB"><span class="nav-number">3.2.2.</span> <span class="nav-text">sentinel的线程隔离</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7"><span class="nav-number">3.3.</span> <span class="nav-text">熔断降级</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%85%A2%E8%B0%83%E7%94%A8"><span class="nav-number">3.3.1.</span> <span class="nav-text">慢调用</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%BC%82%E5%B8%B8%E6%AF%94%E4%BE%8B%E3%80%81%E5%BC%82%E5%B8%B8%E6%95%B0"><span class="nav-number">3.3.2.</span> <span class="nav-text">异常比例、异常数</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%8E%88%E6%9D%83%E8%A7%84%E5%88%99"><span class="nav-number">4.</span> <span class="nav-text">授权规则</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%8E%88%E6%9D%83%E8%A7%84%E5%88%99-1"><span class="nav-number">4.1.</span> <span class="nav-text">授权规则</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%9F%BA%E6%9C%AC%E8%A7%84%E5%88%99"><span class="nav-number">4.1.1.</span> <span class="nav-text">基本规则</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96origin"><span class="nav-number">4.1.2.</span> <span class="nav-text">如何获取origin</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%BB%99%E7%BD%91%E5%85%B3%E6%B7%BB%E5%8A%A0%E8%AF%B7%E6%B1%82%E5%A4%B4"><span class="nav-number">4.1.3.</span> <span class="nav-text">给网关添加请求头</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%85%8D%E7%BD%AE%E6%8E%88%E6%9D%83%E8%A7%84%E5%88%99"><span class="nav-number">4.1.4.</span> <span class="nav-text">配置授权规则</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BC%82%E5%B8%B8%E7%BB%93%E6%9E%9C"><span class="nav-number">4.2.</span> <span class="nav-text">自定义异常结果</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%BC%82%E5%B8%B8%E7%B1%BB%E5%9E%8B"><span class="nav-number">4.2.1.</span> <span class="nav-text">异常类型</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86"><span class="nav-number">4.2.2.</span> <span class="nav-text">自定义异常处理</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%A7%84%E5%88%99%E6%8C%81%E4%B9%85%E5%8C%96"><span class="nav-number">5.</span> <span class="nav-text">规则持久化</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%A7%84%E5%88%99%E7%AE%A1%E7%90%86%E6%A8%A1%E5%BC%8F"><span class="nav-number">5.1.</span> <span class="nav-text">规则管理模式</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#pull%E6%A8%A1%E5%BC%8F"><span class="nav-number">5.1.1.</span> <span class="nav-text">pull模式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#push%E6%A8%A1%E5%BC%8F"><span class="nav-number">5.1.2.</span> <span class="nav-text">push模式</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%AE%9E%E7%8E%B0push%E6%A8%A1%E5%BC%8F"><span class="nav-number">5.2.</span> <span class="nav-text">实现push模式</span></a></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="君不见"
      src="/xlrblog/images/avatar.gif">
  <p class="site-author-name" itemprop="name">君不见</p>
  <div class="site-description" itemprop="description">君不见，黄河之水天上来，奔流到海不复回。</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/xlrblog/archives/">
        
          <span class="site-state-item-count">50</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
        <span class="site-state-item-count">9</span>
        <span class="site-state-item-name">分类</span>
      </div>
      <div class="site-state-item site-state-tags">
        <span class="site-state-item-count">25</span>
        <span class="site-state-item-name">标签</span>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/decxlr" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;decxlr" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2023</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">君不见</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://pisces.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Pisces</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/xlrblog/lib/anime.min.js"></script>
  <script src="/xlrblog/lib/velocity/velocity.min.js"></script>
  <script src="/xlrblog/lib/velocity/velocity.ui.min.js"></script>

<script src="/xlrblog/js/utils.js"></script>

<script src="/xlrblog/js/motion.js"></script>


<script src="/xlrblog/js/schemes/pisces.js"></script>


<script src="/xlrblog/js/next-boot.js"></script>




  




  
<script src="/xlrblog/js/local-search.js"></script>













  

  

</body>
</html>
